/**
 * VYPe11 Project
 * VYPe11 language compiler.
 * Authors: Jozef Lang (xlangj01), 
 * 			Zoltan Zemko (xzemko01)
 * 2011
 */
package cz.vutbr.fit.VYPe.team18.VYPe11.codegeneration.MIPS32;

import java.io.*;
import java.util.*;

import cz.vutbr.fit.VYPe.team18.VYPe11.exception.CodeGenerationException;
import cz.vutbr.fit.VYPe.team18.VYPe11.codegeneration.*;

/**
 * MIPS32 Assembler generator
 */
public final class MIPS32AssemblerGenerator implements IASMGenerator {

	/**
	 * Type size constants in bytes
	 */
	public static final int INTEGER_SIZE = 4;
	public static final int SHORT_SIZE = 2;
	public static final int CHAR_SIZE = 1;
	
	private MIPS32Data _dataRegister;
	private final int _stackBaseAddress;
	
	public MIPS32AssemblerGenerator() {
		_stackBaseAddress = Integer.parseInt("4000", 16);
		_dataRegister = new MIPS32Data(_stackBaseAddress);
	}
	
	@Override
	public void generateAssembler(InputStream TACStream, OutputStream ASMStream) throws CodeGenerationException {
		List<TACStatement> stmtList = null; 
		List<MIPS32Statement> asmList = null; 
		
		try {
			stmtList = parseTACFile(TACStream);
			asmList = generateASMStatements(stmtList);
			// Outputs ASM to file
			for (MIPS32Statement asmStmt : asmList) {
				ASMStream.write(asmStmt.getStatemenent().getBytes());
				ASMStream.write('\n');
			}
		} catch (Exception e) {
			throw new CodeGenerationException(e.getMessage());
		}
	}
	
	/********************************************************************************************/
	/*********** ASM GENERATION FUNCTIOS ********************************************************/
	/********************************************************************************************/
	private List<MIPS32Statement> generateASMStatements(List<TACStatement> TACs) {
		List<MIPS32Statement> asmList = new LinkedList<MIPS32Statement>();
		
		// Generate intro
		asmList.addAll(generateIntro());
		
		// Generate from TACS
		for (TACStatement stmt : TACs) {
			asmList.addAll(generateASMStatement(stmt));
		}
		
		return asmList;
	}
	
	/**
	 * Generates ASM "introduction"
	 * @return
	 */
	private List<MIPS32Statement> generateIntro() {
		List<MIPS32Statement> intro = new LinkedList<MIPS32Statement>();
		intro.add(new MIPS32Statement("# Generated by VYPe2011 MIPS32 Assembler Generator"));
		intro.add(new MIPS32Statement("# Authors: Jozef Lang, Zoltan Zemko"));
		intro.add(new MIPS32Statement("# 2011"));
		intro.add(new MIPS32Statement(""));
		intro.add(new MIPS32Statement(".text"));
		intro.add(new MIPS32Statement(".org 0"));
		intro.add(new MIPS32Statement(String.format("addiu $sp, $0, 0x%s", Integer.toHexString(_stackBaseAddress))));
		//intro.add(new MIPS32Statement("jal main"));
		intro.add(new MIPS32Statement("break"));
		return intro;
	}
	
	/**
	 * Generates appropriate MIPS32Statement for each type of TACstmt
	 * @param TACstmt
	 * @return
	 */
	private List<MIPS32Statement> generateASMStatement(TACStatement TACstmt) {
		List<MIPS32Statement> retList = new LinkedList<MIPS32Statement>();
		
		// Function definition
		if (TACstmt.operator.equals("function")) {
			// function label
			retList.add(new MIPS32Statement(String.format("%s:", TACstmt.operand1)));
		}
		
		/*
		// Function start
		if (TACstmt.operator.equals("begin_function")) {
			// function label
			return new MIPS32Statement(".func");
		}
		
		// Function end
		if (TACstmt.operator.equals("end_function")) {
			// function label
			return new MIPS32Statement(".endfunc");
		}
		*/
		
		// Variable value setting
		if (TACstmt.operator.equals("=")) {
			if (TACstmt.operand1.startsWith("\"")) {
				// String
				_dataRegister.storeString(TACstmt.result, TACstmt.operand1.length());
				retList.add(new MIPS32Statement(String.format("# String stack copy %s", TACstmt.result)));
				for (int i = 0; i < TACstmt.operand1.length(); i++) {
					retList.add(new MIPS32Statement(String.format("li $sp, %d", (int) TACstmt.operand1.charAt(i))));
					retList.add(new MIPS32Statement(String.format("addiu $sp, $sp, %d", CHAR_SIZE)));
				}
				// /0
				retList.add(new MIPS32Statement(String.format("li $sp, %d", (int) '\0')));
				retList.add(new MIPS32Statement(String.format("addiu $sp, $sp, %d", CHAR_SIZE)));
			}
		}		
		
		
		if (retList.size() == 0) {
			retList.add(new MIPS32Statement("nop"));
		}
		return retList;
	}
	
	
	
	/********************************************************************************************/
	/*********** TAC LIST GENERATION FUNCTIOS ***************************************************/
	/********************************************************************************************/
	private List<TACStatement> parseTACFile(InputStream TACStream) throws CodeGenerationException {
		List<TACStatement> retList = new LinkedList<TACStatement>();
		
		try {
			BufferedReader buffrdr = new BufferedReader(new InputStreamReader(TACStream));
			
			// Loop through whole file
			while (true) {
				TACStatement stmt = readTACStatement(buffrdr);
				if (stmt == null)
					break;
				retList.add(stmt);
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
			throw new CodeGenerationException(e.getMessage());
		}
		
		return Collections.unmodifiableList(retList);
	}
	
	private TACStatement readTACStatement(BufferedReader TACStream) throws IOException {
		String readStatement = TACStream.readLine();
		if (readStatement == null || readStatement.isEmpty())
			return null;
		// Ommit ';'
		readStatement = readStatement.endsWith(";") ? readStatement.substring(0, readStatement.length() - 1) : readStatement;
		return parseTACStatement(readStatement);
	}
	
	private TACStatement parseTACStatement(String TACStatement) {
		/**
		 * TACStatement read has following format: [operator, operand1, operand2, result]
		 */
		// Strip first and last [,]
		TACStatement = TACStatement.substring(1, TACStatement.length() - 1);
		StringTokenizer strTok = new StringTokenizer(TACStatement, ",", false);
		if (strTok.countTokens() == 0)
			return null;
		
		// Put all tokens into stringList
		String[] tokens = new String[4];
		for (int i = 0; i < 4; i++)
			tokens[i] = "";
		for (int i = 0; i < 4 && strTok.hasMoreTokens(); i++) {  
			tokens[i] = strTok.nextToken().trim();
		}
		
		return new TACStatement(tokens[0], tokens[1], tokens[2], tokens[3]);
	}

}
